# -*- coding:utf-8 -*-
"""
Created on 25 Nov 2009
"""
from __future__ import division
__author__ = 'Ilias Karampatsos'


# -*- coding: latin-1 -*-

from numpy import average,array,std,sqrt,ceil,floor,mat,random,eye,ones
from time import clock,strftime,localtime

class LogMessages():
    def __init__(self):
        self.myTIME=clock()
        self.messages=0
        self.mystats=IncrementalStats()
        self.extraStats=False
    def stopwatch(self):
        '''This will reset the timer and quickly give the time passed to the msg call'''
        self.__myMsg('',showInConsole=False)
    def msg(self,msg,showInConsole=True):
        self.__myMsg(msg,showInConsole)
    def __myMsg(self,msg,showInConsole=True):
        if self.messages==0:
            if showInConsole:print '%s   \t: %s'% (strftime("%H:%M:%S", localtime()),msg)
            self.myTIME=clock()
            diff=None
        else:
            diff=clock()-self.myTIME
            self.mystats.addSamlpe(diff*1000)
            if self.extraStats:
                if showInConsole:print '%2.2f ms (%2.2f,%2.2f,%2.2f,%2.2f,%2.2f)\t: %s' % (diff*1000,self.mystats.getAverage(),
                                                   self.mystats.getStdDev(),self.mystats.getAverageError(),
                                                    self.mystats.getMin(),self.mystats.getMax(),msg)
            else:
                if showInConsole:print '%2.2f ms \t: %s' % (diff*1000,msg)
                
            self.myTIME=clock()
        self.messages+=1
        return diff
    def reset(self):
        self.messages=0
        self.mystats.reset()
class FreezeState():
    def __init__(self,props,suclass):
        self.props=props
        self.suclass=suclass
        self.stateFrozen={}
        self.isFrozen=False
    def getFrozenState(self):
        if not self.isFrozen:raise ValueError('Class %s was not frozen.' % self.suclass)
        return self.stateFrozen
    def freezeState(self):
        import copy
        self.stateFrozen={}
        for prop in self.props.keys():
            print 'Freezing state: \t%s --> %s' % (prop.upper(),getattr(self,prop))
            self.stateFrozen[prop]=copy.deepcopy(getattr(self.suclass,prop))
        self.isFrozen=True
class IncrementalStats(FreezeState):
    def __init__(self):
        self.reset()
        self.setStoreSamlpes=False
        self.histogramBuffer=30
        self.minHistBins=30
        self.saveProps()
        
        FreezeState.__init__(self,self.props,self)
    def saveProps(self):
        self.props={
            'average':self.average,
            'variance':self.variance,
            'sample':self.sample,
            'hist':self.hist,
            'samlpesContainer':self.samlpesContainer,
            'samlpeSize':self.samlpeSize,
            'setStoreSamlpes':self.setStoreSamlpes,
            'histogramBuffer':self.histogramBuffer,
            'minHistBins':self.minHistBins
            }
    def reset(self):
        self.average={'cur':None,'old':None}
        self.variance={'cur':None,'old':None}
        self.sample={'cur':None,'old':None,'min':None,'max':None}
        self.hist={'buffer':[],'bins':{},'binSize':None,'min':None,'max':None}
        self.samlpesContainer=[]
        self.samlpeSize=0
        self.stateFrozen={}

    def setStoreSamlpes(self,setValue):
        self.setStoreSamlpes=setValue
    def getState(self):
        self.freezeState()
        return self.getFrozenState()
    def setState(self,props):
        for prop in props.keys():
            print 'Restoring state: \t%s --> %s' % (prop.upper(),props[prop])
            setattr(self,prop,props[prop])
    def sortDict(self,dict):
        keys = dict.keys()
        keys.sort()
        sorted={}
        for key in keys:
            sorted[key]=dict[key]
        return sorted

    def addSamlpe(self,samlpe):
        self.samlpeSize+=1
        self.sample['old']=self.sample['cur']
        self.average['old']=self.average['cur']
        self.variance['old']=self.variance['cur']
        self.sample['cur']=samlpe

        if self.samlpeSize==1:
            self.initIncrementalStats()
        else:
            self.updateIncrementalStats()

        if self.samlpeSize<self.histogramBuffer:
            self.hist['buffer'].append(samlpe)
            self.hist['binSize']=self.getOptimalHistogramBinSize()
            self.hist['min']=self.sample['min']
            self.hist['max']=self.sample['max']
        elif self.samlpeSize==self.histogramBuffer:
            self.hist['buffer'].append(samlpe)
            self.updateHistogramBuffer()
        else:
            self.updateHistogram(samlpe)

        if self.setStoreSamlpes:
            self.samlpesContainer.append(samlpe)
    def initIncrementalStats(self):
        self.average['old']=self.sample['cur']
        self.average['cur']=self.average['old']
        self.variance['cur']=0.0
        self.variance['old']=0.0
        self.sample['min']=self.sample['cur']
        self.sample['max']=self.sample['cur']
    def updateIncrementalStats(self):
        if self.sample['cur']>self.sample['max']:
            self.sample['max']=self.sample['cur']
        if self.sample['cur']<self.sample['min']:
            self.sample['min']=self.sample['cur']

        if self.samlpeSize>1:
            delta=self.sample['cur']-self.average['old']
            self.average['cur']=self.average['old']+delta/self.samlpeSize
            n=self.samlpeSize
            self.variance['cur']=((n-2)*self.variance['old']+((n-1)/n)*((self.sample['cur']-self.average['old'])**2))/(n-1)
    def updateHistogramBuffer(self):
        if self.hist['buffer']==None:return
        print 'Buffer distribution'
        for smpl in self.hist['buffer']:
            self.updateHistogram(smpl)
        self.hist['buffer']=None
        print 'Buffer distribution END'
    def updateHistogram(self,sample):
        a=self.allocateToBin(sample)
        try:
            self.hist['bins'][a]+=1
        except KeyError:
            self.hist['bins'][a]=1
    def allocateToBin(self,sample):
        bin=int(ceil(sample/self.hist['binSize']))
        if bin<=0: #center around 0, as 0 now means (-1,0]
            bin-=1
        if sample>self.hist['binSize']*(ceil(self.hist['max']/self.hist['binSize'])+1): #Grace +1
            binStr='>'
        elif sample<self.hist['binSize']*(floor(self.hist['min']/self.hist['binSize'])-1):#Grace -1
            binStr='<'
        else:
            binStr=long(bin)
        #print bin,binStr,sample,self.hist['binSize']
        #raw_input('hey hey')
        return binStr
    def getOptimalHistogramBinSize(self):
        W_Scott=3.49*self.getStdDev()*pow(self.samlpeSize,-1/3)
        minBin=abs(self.sample['max']-self.sample['min'])/self.minHistBins
        return min(W_Scott,minBin)
    def getOptimalHistogramBinCount(self):
        return int(abs(self.sample['max']-self.sample['min'])/self.getOptimalHistogramBinSize())
    def getStats(self):
        if self.samlpeSize<1: raise ValueError('At least 2 samlpes needed to get this.')
        return (self.samlpeSize,self.getAverage(),self.getStdDev(),self.getAverageError(),self.getMin(),self.getMax())
    def getAverage(self):
        if self.samlpeSize== 0: raise ValueError('At least 1 samlpe needed to get this.')
        return self.average['cur']
    def getVariance(self):
        if self.samlpeSize== 0: raise ValueError('At least 1 samlpe needed to get this.')
        return self.variance['cur']
    def getStdDev(self):
        return pow(self.variance['cur'],0.5)
    def getAverageError(self):
        if self.samlpeSize== 0: raise ValueError('At least 1 samlpe needed to get this.')
        return pow(self.variance['cur']/self.samlpeSize,0.5)
    def getMin(self):
        if self.samlpeSize== 0: raise ValueError('At least 1 samlpe needed to get this.')
        return self.sample['min']
    def getMax(self):
        if self.samlpeSize== 0: raise ValueError('At least 1 samlpe needed to get this.')
        return self.sample['max']
    def getHistogramData(self):
        if self.samlpeSize<2: raise ValueError('At least 2 samlpes needed to get this.')
        self.updateHistogramBuffer()
        return self.sortDict(self.hist['bins'])
    def getHistogramDataPcnt(self):
        data=self.getHistogramData()
        bins=data.keys()
        hasL=False
        hasG=False
        if '<' in bins:
            hasL=True
            probL=data['<']/self.samlpeSize
            bins.remove('<')
        if '>' in bins:
            hasG=True
            probG=data['>']/self.samlpeSize
            bins.remove('>')
        bins.sort()
        out=[]
        for bin in bins:
            binReal=round((bin*self.hist['binSize']+self.hist['binSize']/2),3)
            prob=data[bin]/self.samlpeSize
            out.append((bin,binReal,data[bin],prob))
        if hasG:out.append(('>','>',data['>'],probG))
        if hasL: out.insert(0,('<','<',data['<'],probL))
        #print self.hist['binSize']
        #s=raw_input('hey hey:')
        return out
class MatUtils():
    def __init__(self,rows=10000,cols=45):
        self.rows=rows
        self.cols=cols
        self.reset()
    def reset(self):
        try:
            del(self.matrix)
            del(self.y)
        except AttributeError:
            pass
        self.matrix=None
        self.y=None
        self.loadMatrix()
    def loadMatrix(self):
        self.matrix=self.getRandomMatrix((self.rows,self.cols))
        self.y=self.getRandomMatrix((self.rows,1))
    def getMatrix(self):
        b=self.matrix
        return b
    def getPseudoInverse(self):
        return (self.matrix.T*self.matrix).I*self.matrix.T
    def getMatrixDiffMultiplier(self):
        return mat(self.cols*eye(self.cols)-ones(1))
    def getMatrixDiffRowSums(self):
        m=self.getMatrix()*self.getMatrixDiffMultiplier()
        return m
    def getMatrixDiffSums(self):
        a=mat(self.getMatrixDiff().T)*mat(ones(self.rows)).T
        return a
    def getMatrixDiffSumsAvg(self):
        a=self.getMatrixDiffSums()/(self.rows*(self.cols-1))
        return a
    def getMatrixDiffOfCol(self,col):
        a=mat(self.matrix-self.matrix[:,col])[:,col+1:].flatten()
        return a
    def getCholesky(self):
        from numpy.linalg import cholesky
        return cholesky(self.getMatrix())
    def randomise(self):
        self.loadMatrix()
    def getCol(self,mat,col):
        return mat[:,col]
    def getRow(self,row):
        return mat[row]
    def testSolve(self):
        return self.getPseudoInverse()*self.y
    #def testSolveLinLang(self):
    #    return linalg.solve(self.matrix.T*self.matrix,self.matrix.T*self.y)
    def getRandomMatrix(self,dimensions):
        b=random.random(dimensions)
        a=mat(floor(10*b))
        return a
    def getRandomArray(self,dimensions):
        a=array(floor(10*random.random(dimensions)))
        return a
    def getRandomNumber(self):
        return floor(10*random.random())
    def refreshData(self):
        self.randomise()
    def reloadData(self,rows,cols):
        oldRows=self.rows
        oldCols=self.cols
        self.rows=rows
        self.cols=cols
        try:
            self.reset()
        except MemoryError:
            self.rows=oldRows
            self.cols=oldCols
            print 'Too much!'
class SparseMatrix2D:
    def __init__(self):
        self.matrix={}
        self.itemCountsRow={}
        self.itemCountsCol={}
    def addItem(self,row,col,val):
        self.matrix['%s,%s'% (row,col)]=val
        try:
            self.itemCountsRow[str(row)]=self.itemCountsRow[str(row)]+1
        except KeyError:
            self.itemCountsRow[str(row)]=1
        try:
            self.itemCountsCol[str(col)]=self.itemCountsCol[str(col)]+1
        except KeyError:
            self.itemCountsCol[str(col)]=1
    def getItem(self,row,col):
        try:
            return self.matrix['%s,%s'% (row,col)]
        except:
            return None
    def getItems(self):
        for pos in self.matrix.keys():
            yield self.matrix[pos]
    def getSparseItemsOfRow(self,row):
        try:
            return self.itemCountsRow[str(row)]
        except KeyError:
            return 0

def getRandomdata(rows=20,cols=2,minVal=1,maxVal=10):
    return [[random.randint(minVal, maxVal) for i in range(cols)] for j in range(rows)]